#!/usr/bin/env python
# @Date    : 2023-03-13
# @Author  : Bright (brt2@qq.com)
# @Link    : https://gitee.com/brt2
# @Version : 0.2.4

import os.path

import traceback
from code import InteractiveInterpreter  # InteractiveConsole

class WholeExceptionInterpreter(InteractiveInterpreter):
    """ 然而，依然无法打印出: 异常行的traceback """
    def showtraceback(self):
        self.write(traceback.format_exc())

class RemotePythonInterpreter:
    MAX_OUTSTREAM = 9999
    def __init__(self, mpstack=None, workdir=None, quiet=False):
        import sys
        import io

        self._quiet = quiet  # 是否开启server本地同步输出
        # 初始化交互解释器
        locals_data = {
            "__doc__": None,
            "__name__": "__RpiConsole",
            "__file__": "__UnKnownProxyClient"
        }
        if mpstack:
            locals_data["rpi"] = mpstack
        self.interp = InteractiveInterpreter(locals=locals_data)

        self.stdout = sys.stdout  # 标准输出
        # self.stderr = sys.stderr  # showtraceback()与showsyntaxerror()被写入sys.stderr
        self.rpcout = io.StringIO()
        sys.stdout = self.rpcout
        sys.stderr = self.rpcout

        self.extend_syspath()  # for ipynb project
        self.init_webui()
        # self.run_codelines("print('>>>', rpi)")  # 测试共享内存
        if workdir:
            self.chdir(workdir)

    def run_codelines(self, src: str):
        # 无法直接执行两行赋值语句
        # SyntaxError: multiple statements found while compiling a single statement

        for block in self._split_block(src):
            self.interp.runsource(block, filename="[rpi]")

        data = self.rpcout.getvalue().strip()
        self.rpcout.seek(0)
        self.rpcout.truncate()
        if not self._quiet:
            outstream = f"\n>>>\n{src.strip()}\n"
            if data:
                # 设定最大打印长度
                if len(data) > self.MAX_OUTSTREAM:
                    data = data[:self.MAX_OUTSTREAM]
                outstream += f"\n<<<\n{data}\n\n"
            self.stdout.write(outstream)
            self.stdout.flush()
        return data

    def _auto_runblock(self, src):
        """ 根据interp.runsource()的返回值，判断续接前文；
            由于该方法的执行效率不如预处理，故run_codelines中未采用该方式
        """
        src_block = []
        for line in src.split("\n"):
            src_block.append(line)
            state = self.interp.runsource("\n".join(src_block), filename="[rpi]")
            # self.stdout.write(f"...{line},{state}\n")
            if not state:
                src_block.clear()

    def _split_block(self, src):
        """ 通过代码的预处理，拆分代码块 """
        block_cache = []

        def push_block():
            if len(block_cache) > 1:
                block_string = "\n".join(block_cache)
                block_string += "\n"  # func结构需要追加一个空行
            else:
                block_string = block_cache[0]

            return block_string

        for line in src.split("\n"):
            if not line:
                continue
            if (not line.startswith(" ")) and (not line.startswith("\t")):
                if line.startswith("else") or line.startswith("except") or \
                line.startswith("finally") or (line[0] in [")", "}", "]"]):
                    pass
                elif block_cache:
                    yield push_block()
                    block_cache.clear()

            block_cache.append(line)

        if block_cache:
            yield push_block()

    def extend_syspath(self):
        curr_file = __file__
        if os.path.islink(curr_file):
            curr_file = os.path.realpath(curr_file)
        tooldir = os.path.dirname(os.path.abspath(curr_file))
        rootdir = os.path.dirname(tooldir)
        self.run_codelines("import os,sys; sys.path.append('{}')".format(rootdir))
        # self.run_codelines("print('RPI curdir:', os.path.abspath(os.curdir))")
        # self.run_codelines("from utils import *")  # 预加载功能模块: utils.__init__.py

    def chdir(self, workdir):
        self.run_codelines(f"os.chdir({workdir}); print('CurrDIR:', os.path.abspath(os.curdir))")

    def init_webui(self):
        # 初始化rpi对象，方便client端调用
        self.run_codelines("import rvi; rrapp=rvi.RemoteInterpreterVision(); rr=rrapp.stack; rrapp.show()")


from xmlrpc.server import SimpleXMLRPCServer
from xmlrpc.server import SimpleXMLRPCRequestHandler

# Restrict to a particular path.
class RequestHandler(SimpleXMLRPCRequestHandler):
    rpc_paths = ('/RPC2',)

RPI_SERVICE_PORT = 2180

def make_xmlrpc(rpi, **kwargs):
    kwargs["host"] = kwargs.get("host", "0.0.0.0")
    kwargs["port"] = kwargs.get("port", RPI_SERVICE_PORT)

    server = SimpleXMLRPCServer((kwargs["host"], kwargs["port"]),
                                logRequests=False,
                                requestHandler=RequestHandler)
    server.register_introspection_functions()
    server.register_instance(rpi)
    return server

def run_server(rpi, **kwargs):
    with make_xmlrpc(rpi, **kwargs) as server:
        server.serve_forever()

#####################################################################

def _runClient():
    from xmlrpc.client import ServerProxy
    rpc = ServerProxy('http://127.0.0.1:2180')

    ret = rpc.run_codelines("print('Hello')")
    print(">>>", ret)
    ret = rpc.run_codelines("print 'Hello' ")
    print(">>>", ret)

def _testRPI():
    src = """
def test():
    print("hhh")
    return 20
test()
test
"""
    # print(src)
    rpi = RemotePythonInterpreter()
    rpi.run_codelines(src)


if __name__ == "__main__":
    # _testRPI()
    def getopt():
        import argparse

        parser = argparse.ArgumentParser("idle-server", description="RPCServer for IDLE")
        parser.add_argument("--Host", default="0.0.0.0", help="服务器地址")
        parser.add_argument("--Port", default=RPI_SERVICE_PORT, type=int, help=f"default: {RPI_SERVICE_PORT}")
        parser.add_argument("--startUI", action="store_true", help=f"启动UI微服务（默认）")
        return parser.parse_args()

    args = getopt()

    rpi = RemotePythonInterpreter()
    if args.startUI:
        rpi.init_webui()

    run_server(rpi, host=args.Host, port=args.Port)
